package com.huitong.auth.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.util.ObjectUtil;
import com.huitong.auth.domain.dto.LoginDTO;
import com.huitong.auth.domain.dto.PasswordDTO;
import com.huitong.auth.domain.po.UserPO;
import com.huitong.auth.mapper.TokenMapper;
import com.huitong.auth.properties.UserPasswordProperties;
import com.huitong.auth.service.TokenService;
import com.huitong.common.core.constant.UserConstant;
import com.huitong.common.core.exception.UserException;
import com.huitong.common.redis.constant.CacheConstant;
import com.huitong.common.redis.utils.RedisUtils;
import com.huitong.common.satoken.domain.vo.LoginVO;
import com.huitong.common.satoken.utils.LoginUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.Duration;

/**
 * 令牌服务层
 *
 * @author fromdrowning
 * @date 2024/4/6 10:43
 */
@RequiredArgsConstructor
@Service
public class TokenServiceImpl implements TokenService {

    /**
     * 令牌数据访问层
     */
    private final TokenMapper tokenMapper;

    /**
     * 用户密码配置
     */
    private final UserPasswordProperties userPasswordProperties;

    /**
     * 登录系统
     *
     * @param loginDTO 用户输入的登录信息
     * @return 用户登录信息
     */
    @Override
    public LoginVO login(LoginDTO loginDTO) {
        Long id = loginDTO.getId();
        String role = loginDTO.getRole();
        UserPO userPO = tokenMapper.selectUserInfo(id, role);

        // 查无此用户抛异常
        if (userPO == null) {
            throw new UserException("账号错误");
        }

        // 账号被禁用抛异常
        if (UserConstant.DISABLE.equals(userPO.getStatus())) {
            throw new UserException("账号已被禁用");
        }

        verifyPassword(id, role, BCrypt.checkpw(loginDTO.getPassword(), userPO.getPassword()));

        return LoginVO.builder()
            .id(id)
            .name(userPO.getName())
            .role(role)
            .build();
    }

    /**
     * 修改密码
     *
     * @param passwordDTO 修改密码信息
     */
    @Override
    public void updatePassword(PasswordDTO passwordDTO) {
        Long id = LoginUtils.getLoginUserId();
        String role = LoginUtils.getLoginUserRole();
        UserPO userPO = tokenMapper.selectUserInfo(id, role);

        String oldPassword = passwordDTO.getOldPassword();
        String newPassword = passwordDTO.getNewPassword();

        // 旧密码输入错误抛异常
        if (!BCrypt.checkpw(oldPassword, userPO.getPassword())) {
            throw new UserException("旧密码错误");
        }

        // 新密码与旧密码相同抛异常
        if (BCrypt.checkpw(newPassword, oldPassword)) {
            throw new UserException("新密码不能与旧密码相同");
        }

        userPO.setPassword(BCrypt.hashpw(newPassword));
        tokenMapper.updatePassword(userPO);
    }

    /**
     * 校验密码 密码错误不能超过最大重试次数
     *
     * @param id           学号
     * @param role         角色
     * @param verifyResult {@code true}校验成功
     */
    private void verifyPassword(Long id, String role, boolean verifyResult) {
        String errorKey = CacheConstant.ERROR_USER_KEY + role + ":" + id;
        int lockTime = userPasswordProperties.getLockTime();
        int maxRetryCount = userPasswordProperties.getMaxRetryCount();
        int errorCount = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);

        // 错误次数超过最大重试次数抛异常
        if (errorCount >= maxRetryCount) {
            throw new UserException("登录超过最大重试次数，请" + lockTime + "分钟后重试");
        }

        // 密码校验失败 增加错误次数并锁定账户
        if (!verifyResult) {
            errorCount++;
            RedisUtils.setCacheObject(errorKey, errorCount, Duration.ofMinutes(lockTime));
            throw new UserException(errorCount >= maxRetryCount ? "登录超过最大重试次数，请" + lockTime + "分钟后重试" : "密码错误");
        }

        // 密码校验成功 删除错误次数缓存
        RedisUtils.deleteObject(errorKey);
    }
}
